/**
 * Copyright 2014 opencxa.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.cxa.rpc.transportChannel.json.webSocket;

import java.io.IOException;

import org.cxa.misc.ExceptionUtils;
import org.cxa.rpc.messages.KeepAliveResponseRpcMessage;
import org.cxa.rpc.messages.KeepAliveRpcMessage;
import org.cxa.rpc.messages.NotificationRpcMessage;
import org.cxa.rpc.messages.RequestRpcMessage;
import org.cxa.rpc.messages.ResponseRpcMessage;
import org.cxa.rpc.messages.RpcMessage;
import org.cxa.rpc.transportChannel.json.JsonTransportChannel;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener;

import com.google.gson.JsonSyntaxException;


/**
 * @author Christopher Armenio
 */
public abstract class WebSocketJsonTransportChannel extends JsonTransportChannel implements WebSocketListener
{
	protected static final int STATUS_CODE_CLOSE_REMOTE = 1000;
	protected static final int STATUS_CODE_CLOSE_LOCAL = 4000;
	protected static final int STATUS_CODE_CLOSE_EXCEPTION_SENDING = 4001;
	
	
	protected Session session = null;
	
	
	@Override
	public void onWebSocketConnect(Session sessionIn)
	{
		this.session = sessionIn;
	}
	
	
	@Override
    public void onWebSocketClose(int statusCodeIn, String reasonStringIn)
	{
		DisconnectReason reason = DisconnectReason.UNKNOWN;
		switch(statusCodeIn)
		{
			case STATUS_CODE_CLOSE_REMOTE:
				reason = DisconnectReason.REMOTE_REQUESTED;
				break;
			
			case STATUS_CODE_CLOSE_LOCAL:
				reason = DisconnectReason.LOCAL_REQUESTED;
				break;
				
			case STATUS_CODE_CLOSE_EXCEPTION_SENDING:
				reason = DisconnectReason.ERROR_SENDING_DATA;
				break;
		}
		
		this.session = null;
		this.notifyListeners_onDisconnect(reason);
	}
	
	
	@Override
	public void onWebSocketError(Throwable cause)
	{
		this.logger.warn(String.format("webSocket error, channel will be closed: '%s'", cause.getMessage()));
		// according to docs, session will be closed automatically
	}
	
	
	@Override
    public void onWebSocketText(String jsonIn)
	{
		try
		{
			RpcMessage msg = this.gson.fromJson(jsonIn, RpcMessage.class);
			if( msg == null ) return;
			
			// if we made it here, we have a valid message...validate it
			if( !msg.getMsgVersion().equals(RpcMessage.MESSAGE_VERSION) )
			{
				this.logger.warn(String.format("received message with unexpected protocol version: '%d'", msg.getMsgVersion()));
				return;
			}
			
			// we need to translate to a proper subclass via a copy constructor
			// there is probably a better way of doing this, but this works for now...
			switch(msg.getMsgType())
			{
				case KEEPALIVE:
					msg = new KeepAliveRpcMessage(msg);
					break;
					
				case KEEPALIVE_RESPONSE:
					msg = new KeepAliveResponseRpcMessage(msg);
					break;
					
				case REQUEST:
					msg = new RequestRpcMessage(msg);
					break;
					
				case RESPONSE:
					msg = new ResponseRpcMessage(msg);
					break;
					
				case NOTIFICATION:
					msg = new NotificationRpcMessage(msg);
					break;
					
				default:
					this.logger.warn(String.format("unknown message type received"));
					return;
			
			}
			this.logger.trace(String.format("msg received (%s): '%s'", msg.toString(), jsonIn));
			
			// now notify our listeners
			this.notifyListeners_onMessageReceived(msg);
		}
		catch( JsonSyntaxException e )
		{
			this.logger.warn(String.format("malformed json received, ignoring: '%s'", jsonIn));
		}
	}
	
	
	@Override
	public void onWebSocketBinary(byte[] payload, int offset, int len)
	{
		this.logger.warn("binary message received");
	}
	
	
	/*
	 * (non-Javadoc)
	 * @see org.cxa.rpc.transportChannel.TransportChannel#disconnect()
	 */
	@Override
	public void disconnect()
	{
		if( this.isConnected() ) this.session.close(STATUS_CODE_CLOSE_LOCAL, "");
	}
	
	
	/*
	 * (non-Javadoc)
	 * @see org.cxa.rpc.transportChannel.TransportChannel#isConnected()
	 */
	@Override
	public boolean isConnected()
	{
		return (this.session != null);
	}
	
	
	/*
	 * (non-Javadoc)
	 * @see org.cxa.rpc.transportChannel.json.JsonTransportChannel#sendJson(java.lang.String)
	 */
	@Override
	public boolean sendJson(String jsonIn)
	{
		if( !this.isConnected() ) return false;
		
		boolean retVal = false;
		try
		{
			this.session.getRemote().sendString(jsonIn);
			retVal = true;
		}
		catch(IOException e)
		{
			this.logger.error(ExceptionUtils.getExceptionLogString(e, "error sending data, channel will be closed"));
			this.session.close(STATUS_CODE_CLOSE_EXCEPTION_SENDING, e.toString());
		}
		
		return retVal;
	}
}
